#include "PrecompiledHeaders.h"
#include "AudioInterface.h"
#include "Constants.h"

/*
 * Constructor
 */
AudioInterface::AudioInterface(void)
{
    ALenum error;

    // Initialise alut and listener position
    alutInit(NULL, NULL);
	alGetError();
	initListenerValues();
	
	// Seed random
	srand((unsigned)time(0));
	
	// Add the BGM sources
	bgmFiles[0] = std::string(Constants::AUDIO_FOLDER+"bgm/twinned.wav");
    bgmFiles[1] = std::string(Constants::AUDIO_FOLDER+"bgm/decibel.wav");
    bgmFiles[2] = std::string(Constants::AUDIO_FOLDER+"bgm/grime.wav");
    bgmFiles[3] = std::string(Constants::AUDIO_FOLDER+"bgm/mellow.wav");
	
	// Create BGM source
	alGenSources(1, &bgmSource);
   	if((error = alGetError()) != AL_NO_ERROR) {
   	    fprintf(stderr, "Error: %s: generating BGM source\n", getALErrorString(error));
   	}
   	setDefaultSourceParams(bgmMenuSource);
    
    // Create the menu BGM
    alGenSources(1, &bgmMenuSource);
    if((error = alGetError()) != AL_NO_ERROR) {
   	    fprintf(stderr, "Error: %s: generating menu BGM source\n", getALErrorString(error));
   	}
   	std::string menuBGMFilename = Constants::AUDIO_FOLDER+"bgm/decibel_intro.wav";
   	bgmMenuBuffer = alutCreateBufferFromFile(menuBGMFilename.c_str());
   	if(bgmMenuBuffer == AL_NONE) {
        fprintf(stderr, "Error: %s: could not create bgmMenuBuffer for %s\n", 
            alutGetErrorString(alutGetError()), menuBGMFilename.c_str());
    }
    alSourcei(bgmMenuSource, AL_BUFFER, bgmMenuBuffer);
    setDefaultSourceParams(bgmMenuSource);
    playingMenuBGM = false;
    
    // Menu Sounds
    createSource(AUDIO_MENU_OVER,    relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"menu_click.wav");
    createSource(AUDIO_MENU_CLICK,   relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"menu_click.wav");
    createSource(AUDIO_MENU_LEVEL,   relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"menu_click.wav");
    
    // Game Sounds
    createSource(AUDIO_COLLISION,    relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"menu_click.wav");
    createSource(AUDIO_FALLOUT,      relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"die.wav");
    createSource(AUDIO_ENDZONE,      relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"end_zone.wav");
    createSource(AUDIO_COLLECT1,     relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"score_pickup1.wav");
	createSource(AUDIO_COLLECT2,     relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"score_pickup2.wav");
	createSource(AUDIO_COLLECT3,     relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"score_pickup3.wav");
	createSource(AUDIO_LIFEUP,		 relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"lifeup.wav");
	createSource(AUDIO_POWERDOWN,    relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"powerdown.wav");
	createSource(AUDIO_POWERDOWN_OFF,relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"powerdown_off.wav");
    createSource(AUDIO_TELEPORT_IN,  relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"teleport_in.wav");
    createSource(AUDIO_TELEPORT_OUT, relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"teleport_out.wav");
	createSource(AUDIO_GROW,		 relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"grow.wav");
    createSource(AUDIO_SHRINK,		 relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"shrink.wav");
	createSource(AUDIO_EXTRATIME,	 relative, AUDIO_SFX, Constants::AUDIO_FOLDER+"extra_time.wav");
}

void AudioInterface::setDefaultSourceParams(ALuint source)
{
    ALfloat zero_vector[] = {0.0, 0.0, 0.0};
    alSourcef(source,  AL_PITCH, 1.0f);
    alSourcef(source,  AL_GAIN, 1.0f);
    alSourcefv(source, AL_POSITION, zero_vector);
    alSourcefv(source, AL_VELOCITY, zero_vector);
    alSourcei(source,  AL_LOOPING, true);
}

/*
 * Initialise OpenAL: Open audio device and create context
 */
ALboolean AudioInterface::init()
{
    ALenum error;
    //device = alcOpenDevice(NULL);
    device = alcOpenDevice("DirectSound3D");
    if(!device) {
        fprintf(stderr, "Could not open audio device\n");
        return AL_FALSE;
    }
    printf("Using audio device '%s'\n", alcGetString(device, ALC_DEVICE_SPECIFIER));
    context = alcCreateContext(device, NULL);
    alcMakeContextCurrent(context);
    if(!context) {
        fprintf(stderr, "Could not create audio context\n");
        return AL_FALSE;
    }
    if((error = alcGetError(device)) != ALC_NO_ERROR) {
        fprintf(stderr, "Error: %s: initialising context\n", getALCErrorString(error));
    }
    return AL_TRUE;
}

/*
 * Destructor
 */
AudioInterface::~AudioInterface()
{
    // AL cleanup: delete and sources, buffers and clear vectors
    for(unsigned int i=0; i<sources.size(); i++) {
        alDeleteSources(1, &sources[i]);
    }
    for(unsigned int i=0; i<buffers.size(); i++) {
        alDeleteBuffers(1, &buffers[i]);
    }
    sources.clear();
    buffers.clear();
    loadedFiles.clear();

    // ALUT cleanup
	alutExit();
	
	// ALC cleanup
    /*ALCcontext* pCurContext;
    ALCdevice* pCurDevice;
    pCurContext = alcGetCurrentContext();
    pCurDevice = alcGetContextsDevice(pCurContext);
    alcMakeContextCurrent(NULL);
    alcDestroyContext(pCurContext);
    alcCloseDevice(pCurDevice);*/
}

/*
 * Copy constructor
 */
AudioInterface::AudioInterface(const AudioInterface& o)
{
}

/*
 * Equality override
 */
AudioInterface& AudioInterface::operator=(AudioInterface&)
{
    return *this;
}

/*
 * Initialise listener position, velocity and orientation
 */
void AudioInterface::initListenerValues()
{
    ALvector vzero;
    vzero.x = 0;
    vzero.y = 0;
    vzero.z = 0;
    listenerPos = vzero;
    listenerVel = vzero;
    listenerOri.at = vzero;
    listenerOri.up = vzero;
    alListenerfv(AL_POSITION,   (ALfloat *)&listenerPos);
    alListenerfv(AL_VELOCITY,   (ALfloat *)&listenerVel);
    alListenerfv(AL_ORIENTATION,(ALfloat *)&listenerOri);
}

void AudioInterface::loadRandomBGM()
{    
    // load the new file
    int index = rand() % NUM_BGM_FILES;
    std::string filename = bgmFiles[index];
    printf("Loading random bgm file: %s, index %i\n",filename.c_str(), index);
    bgmBuffer = alutCreateBufferFromFile(filename.c_str());
    
    // Bind to buffer and set looping
    alSourcei(bgmSource, AL_BUFFER, bgmBuffer);
    alSourcei(bgmSource, AL_LOOPING, true);
}

void AudioInterface::playBGM() {
    printf("Playing BGM\n");
    alSourcePlay(bgmSource);
}

void AudioInterface::pauseBGM() {
    printf("Paused BGM\n");
    alSourcePause(bgmSource);
}

void AudioInterface::stopBGM() {
    printf("Stopping BGM\n");
    alSourceStop(bgmSource);
}

void AudioInterface::playMenuBGM() {
    printf("Playing menu BGM\n");
    alSourcePlay(bgmMenuSource);
    playingMenuBGM = true;
}

void AudioInterface::stopMenuBGM() {
    printf("Stopping menu BGM\n");
    alSourceStop(bgmMenuSource);
    playingMenuBGM = false;
}

void AudioInterface::setListenerPosition(Ogre::Vector3 position)
{
    alListenerfv(AL_POSITION, (ALfloat *)position.ptr());
}

void AudioInterface::setListenerVelocity(Ogre::Vector3 velocity)
{
    alListenerfv(AL_VELOCITY, (ALfloat *)velocity.ptr());
}

void AudioInterface::setListenerOrientation(Ogre::Vector3 at, Ogre::Vector3 up)
{
    ALorientation tmp;
    tmp.at.x = (ALfloat)at.x;
    tmp.at.y = (ALfloat)at.y;
    tmp.at.z = (ALfloat)at.z;
    tmp.up.x = (ALfloat)up.x;
    tmp.up.y = (ALfloat)up.y;
    tmp.up.z = (ALfloat)up.z;
    alListenerfv(AL_ORIENTATION, (ALfloat *)&tmp);
}

/*
 * Load a WAV sample into a new source, position = origin
 */
int AudioInterface::createSource(SourceIndex index, SourcePosition position, SourceType type, std::string file)
{
    return createSource(index, position, type, file, Ogre::Vector3(0,0,0));
}

/*
 * Load a WAV sample into a new source, with specified position
 */
int AudioInterface::createSource(SourceIndex index, SourcePosition position, 
    SourceType type, std::string file, Ogre::Vector3 positionOffset)
{
    //ALuint source;
    ALuint source;
    ALuint buffer;
    ALenum error;

    // get the buffer id
    buffer = getBuffer(file);
    
    // create sources and bind with the buffer
   	alGenSources(1, &source);
   	if((error = alGetError()) != AL_NO_ERROR) {
   	    fprintf(stderr, "Error: %s: generating audio source\n", getALErrorString(error));
   	}
   	
   	// bind to buffer
   	alSourcei(source, AL_BUFFER, buffer);
   	
   	// set positioning type
   	if(position == relative) {
   	    alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
   	}
   	
   	// add to vector
   	it = sources.begin();
   	sources.insert(it+(int)index, source);
   	int sourceIndex = (int)sources.size() - 1;
   	
   	// set the sound properties
   	setSourcePitch((SourceIndex)sourceIndex, 1.0f);
   	setSourceGain((SourceIndex)sourceIndex, 1.0f);
   	setSourcePosition((SourceIndex)sourceIndex, positionOffset);
   	setSourceVelocity((SourceIndex)sourceIndex, Ogre::Vector3(0,0,0));
   	setSourceLoop((SourceIndex)sourceIndex, false);
   
    return sourceIndex;
}

/*
 * Get a buffer, load if necessary
 */
ALuint AudioInterface::getBuffer(std::string file)
{
    ALuint buffer;
    
    // search loaded files
    for(unsigned int i=0; i<loadedFiles.size(); i++) {
        if(loadedFiles[i].compare(file) == 0)
            return buffers[i];
    }
    
    // other wise load in from memory
    buffer = alutCreateBufferFromFile(file.c_str());
    if(buffer == AL_NONE) {
        fprintf(stderr, "Error: %s: could not create buffer for %s\n", 
            alutGetErrorString(alutGetError()), file.c_str());
    }
    
    // add to vectors and return
    buffers.push_back(buffer);
    loadedFiles.push_back(file);
    return buffer;
}

void AudioInterface::setSourcePitch(SourceIndex index, float pitch)
{
    alSourcef(sources[(int)index], AL_PITCH, pitch);
}

void AudioInterface::setSourceGain(SourceIndex index, float gain)
{
    alSourcef(sources[(int)index], AL_GAIN, gain);
}

void AudioInterface::setSourcePosition(SourceIndex index, Ogre::Vector3 positionOffset)
{
    alSourcefv(sources[(int)index], AL_POSITION, (ALfloat *)positionOffset.ptr());
}

void AudioInterface::setSourceVelocity(SourceIndex index, Ogre::Vector3 velocity)
{
    alSourcefv(sources[(int)index], AL_VELOCITY, (ALfloat *)velocity.ptr());
}

void AudioInterface::setSourceLoop(SourceIndex index, bool loop)
{
    alSourcei(sources[(int)index], AL_LOOPING,  loop);
}

// Sets the volume of the BGM sources by looping through all sound effects (given by start and end index)
void AudioInterface::setVolumeBGM(float volume)
{
    //printf("setting bgm volume: %f\n", volume);
    alSourcef(bgmSource, AL_GAIN, volume);
    alSourcef(bgmMenuSource, AL_GAIN, volume);
}

// Sets the volume of the SFX sources by looping through all sound effects (given by start and end index)
void AudioInterface::setVolumeSFX(float volume)
{
    for (unsigned int i=0; i<sources.size(); i++) {
        setSourceGain((SourceIndex)i, volume);
    }
}

/*
 * Play, pause, stop a particular source
 */
 void AudioInterface::playSource(SourceIndex i)
 {
     alSourcePlay(sources[(int)i]);
 }
 
 void AudioInterface::pauseSource(SourceIndex i)
 {
     alSourcePause(sources[(int)i]);
 }
 
 void AudioInterface::stopSource(SourceIndex i)
 {
     alSourceStop(sources[(int)i]);
 }
 
const char *AudioInterface::getALErrorString(ALenum error)
{
    switch(error) {
        case AL_NO_ERROR:
            return "AL_NO_ERROR";
        case AL_INVALID_NAME:
            return "AL_INVALID_NAME";
        case AL_INVALID_ENUM:
            return "AL_INVALID_ENUM";
        case AL_INVALID_VALUE:
            return "AL_INVALID_VALUE";
        case AL_INVALID_OPERATION:
            return "AL_INVALID_OPERATION";
        case AL_OUT_OF_MEMORY:
            return "AL_OUT_OF_MEMORY";
        default:
            return "Unrecognised error";
    };
}

const char *AudioInterface::getALCErrorString(ALenum error)
{
    switch(error) {
        case ALC_NO_ERROR:
            return "AL_NO_ERROR";
        case ALC_INVALID_DEVICE:
            return "ALC_INVALID_DEVICE";
        case ALC_INVALID_CONTEXT:
            return "ALC_INVALID_CONTEXT";
        case ALC_INVALID_ENUM:
            return "ALC_INVALID_ENUM";
        case ALC_INVALID_VALUE:
            return "ALC_INVALID_VALUE";
        case ALC_OUT_OF_MEMORY:
            return "ALC_OUT_OF_MEMORY";
        default:
            return "Unrecognised error";
    };
}
